<template>
    <div class="tip">
        <div class="tip_box">
            <b>TIP：</b>
            <div class="tip_section">
                1、基于WebRTC，没有连接TURN服务器，可能无法内网穿透，只能在局域网内的2台设备通信；<br />
                2、在同一台电脑上试验，可以使用同一个浏览器不同Tab打开，其中一个Tab访问
                <a href="https://yuhuo.online/yho-show/#/javascript/video-call?uid=yho_2506090003" target="blank">https://yuhuo.online/yho-show/#/javascript/video-call?uid=yho_2506090003</a>
                （通过uid参数指定用户，模拟不同用户之间的通信）<br />
            </div>
        </div>
    </div>
    <div class="container">
        <div class="row">
            <label>我的连接码：</label>
            <span class="row_code">{{ info.localCode }}</span>
        </div>
        <div class="row">
            <label>对方连接码：</label>
            <el-input class="input" v-model="info.remoteCode" :readonly="info.status != 0" />
            <el-button type="primary" v-if="info.status == 0" @click="startInvite()">连接邀请</el-button>
            <el-button type="primary" v-else-if="info.status == 1" loading>连接中</el-button>&nbsp;
            <el-button type="danger" v-else @click="closeConnect(true)">关闭连接</el-button>
        </div>
        <div class="row">
            <label>对方画面：</label>
            <video class="remote_video" ref="remoteVedio"></video>
            <label>我的画面：</label>
            <video class="local_video" ref="localVedio"></video>
        </div>
    </div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, watch } from "vue";
import { useUserStore } from "@/store/user";
import api from "@/api/api";
import { WebSocketURL } from "@/utils/request";
import { ElMessage, ElMessageBox } from "element-plus";

const { user } = useUserStore();
const remoteVedio = ref<HTMLVideoElement>();
const localVedio = ref<HTMLVideoElement>();

let info = reactive({
    status: 0, // 状态：0 空闲，1 邀请中等待回应，2 连接中
    localUid: user.uid,
    localCode: user.code,
    remoteUid: null,
    remoteCode: "",
});

const { s } = window as any;
// WebRTC 对象
let connection: RTCPeerConnection;
// WebSocket 对象
let ws: WebSocket;
// 候选地址缓存数组
let pendingCandidates: RTCIceCandidateInit[] = [];

onMounted(() => {
    // 建立连接
    let query = new URLSearchParams(s({ uid: info.localUid }));
    ws = new WebSocket(WebSocketURL + "?" + query);
    // 接收服务器消息
    ws.onmessage = function (event) {
        console.log(JSON.parse(event.data));
        // ** A方向B方连接 **
        let { type, localUid: remoteUid, localCode: remoteCode, description, candidate } = JSON.parse(event.data);

        // B方不在线，A方收到回调
        if (type == "offline") {
            info.status = 0;
            ElMessage({ message: "对方未在线", type: "warning" });
        }
        // B方忙线中，A方收到回调
        if (type == "busy") {
            info.status = 0;
            ElMessage({ message: "对方忙线，请稍后重试", type: "warning" });
        }
        // B方拒绝邀请，A方收到回调
        if (type == "rejectInvite") {
            info.status = 0;
            ElMessage({ message: "对方拒绝连接", type: "warning" });
        }
        // B方接受邀请，A方收到回调
        if (type == "acceptInvite") {
            startConnect();
        }
        // A方收到B方的连接响应
        if (type == "answerConnect") {
            connection.setRemoteDescription(description).then(() => {
                pendingCandidates.forEach((c) => connection.addIceCandidate(c));
                pendingCandidates = [];
            });
        }

        // B方收到A方邀请
        if (type == "startInvite") {
            answerInvite(remoteUid, remoteCode);
        }
        // B方收到A方的连接请求
        if (type == "startConnect") {
            answerConnect(description);
        }

        // 对方提供了新的候选地址
        if (type == "iceCandidate") {
            // 连接对象已经创建并设置了对方信令，直接添加
            if (connection && connection.remoteDescription) {
                connection.addIceCandidate(candidate);
            } else {
                // 否则暂时存入缓存数组，等待添加
                pendingCandidates.push(candidate);
            }
        }
        // 通知对方关闭连接
        if (type == "closeConnect") {
            closeConnect(false);
        }
    };
});

// A方发起邀请
async function startInvite() {
    if (info.remoteCode.length != 6) {
        ElMessage({ message: "请输入正确的连接码", type: "warning" });
        return;
    }
    if (info.remoteCode == info.localCode) {
        ElMessage({ message: "请勿输入自己的连接码", type: "warning" });
        return;
    }
    info.status = 1;
    // 根据B方连接码，获取B方uid
    let res = await api.userGetUid({ code: info.remoteCode });
    if (res.status != 200) {
        info.status = 0;
        ElMessage({ message: res.message, type: "warning" });
        return;
    }
    info.remoteUid = res.data;

    // 发起一个邀请，等待对方回复
    send({
        type: "startInvite",
        localUid: info.localUid,
        localCode: info.localCode,
        remoteUid: info.remoteUid,
    });
}
// B方响应邀请
function answerInvite(remoteUid: string, remoteCode: string) {
    // B方此时非空闲
    if (info.status != 0) {
        send({ type: "busy", remoteUid });
        return;
    }
    info.status = 1;
    ElMessageBox.confirm(`${remoteCode} 向您发起连接邀请，是否接受？`, "连接邀请", {
        confirmButtonText: "接受",
        cancelButtonText: "拒绝",
    })
        .then(() => {
            // 接受连接
            send({ type: "acceptInvite", remoteUid });
            info.remoteUid = remoteUid;
            info.remoteCode = remoteCode;
        })
        .catch(() => {
            // 拒绝连接
            info.status = 0;
            send({ type: "rejectInvite", remoteUid });
        });
}

// A方发起连接
async function startConnect() {
    // 初始化连接
    await initConnect();
    // 生成A方信令，发送给B方
    await connection.setLocalDescription(await connection.createOffer());
    send({
        type: "startConnect",
        remoteUid: info.remoteUid,
        description: connection.localDescription,
    });
    info.status = 2;
}
// B方响应连接
async function answerConnect(remoteDescription: RTCSessionDescriptionInit) {
    // 初始化连接，保存A方信令
    await initConnect();
    await connection.setRemoteDescription(remoteDescription);
    // 生成B方信令
    await connection.setLocalDescription(await connection.createAnswer());
    // 添加缓存的候选地址
    pendingCandidates.forEach((c) => connection.addIceCandidate(c));
    pendingCandidates = [];

    // 响应发送回给A方
    send({
        type: "answerConnect",
        remoteUid: info.remoteUid,
        description: connection.localDescription,
    });
    info.status = 2;
}

// 连接初始化
async function initConnect() {
    connection = new RTCPeerConnection({
        iceServers: [{ urls: "stun:stun.voipbuster.com" }, { urls: "stun:stun.l.google.com:19302" }],
    });
    // 监听远程媒体流事件
    connection.ontrack = function (event) {
        if (!remoteVedio.value.srcObject) {
            remoteVedio.value.srcObject = event.streams[0];
            remoteVedio.value.play();
        }
    };
    // 监听ice服务器返回新候选地址事件
    connection.onicecandidate = (event) => {
        if (event.candidate) {
            send({
                type: "iceCandidate",
                candidate: event.candidate,
                remoteUid: info.remoteUid,
            });
        }
    };
    // 采集本地媒体流
    const localStream = await navigator.mediaDevices.getUserMedia({
        video: true,
        audio: true,
    });
    // 设置本地媒体流
    localVedio.value.srcObject = localStream;
    localVedio.value.play();

    // 本地媒体流的轨道都添加到 RTCPeerConnection 中
    localStream.getTracks().forEach((track) => {
        connection.addTrack(track, localStream);
    });
}

// 关闭连接
function closeConnect(isSelf: boolean) {
    // 是自己主动关闭的，要通知对方关闭
    if (isSelf) {
        send({ type: "closeConnect", remoteUid: info.remoteUid });
    }
    // 释放媒体资源
    if (localVedio.value.srcObject instanceof MediaStream) {
        localVedio.value.srcObject.getTracks().forEach((track) => track.stop());
    }
    // 关闭视频
    localVedio.value.srcObject = null;
    remoteVedio.value.srcObject = null;
    // 关闭连接对象
    connection.close();
    connection = null;
    // 重置状态
    info.status = 0;
    ElMessage({ message: "连接已关闭", type: "warning" });
}

// 定义websocket数据格式
type WebSocketData =
    | { type: "startInvite"; remoteUid: string; localUid: string; localCode: string }
    | { type: "offline" }
    | { type: "busy"; remoteUid: string }
    | { type: "acceptInvite"; remoteUid: string }
    | { type: "rejectInvite"; remoteUid: string }
    | { type: "startConnect"; remoteUid: string; description: RTCSessionDescriptionInit }
    | { type: "answerConnect"; remoteUid: string; description: RTCSessionDescriptionInit }
    | { type: "iceCandidate"; remoteUid: string; candidate: RTCIceCandidateInit }
    | { type: "closeConnect"; remoteUid: string };
// 发送方法
function send(data: WebSocketData) {
    ws.send(JSON.stringify(data));
}

watch(
    () => info.remoteCode,
    (val) => {
        if (val) {
            info.remoteCode = val.replace(/\D/g, "").slice(0, 6);
        }
    }
);
</script>
<style scoped lang="scss" src="./video-call.scss"></style>
